﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Forms;
using DriveBeamEditor.Controls.BeamNG.VehiclePartConfig;
using JBeamLibrary;
using JBeamLibrary.VehicleParts;
using JBeamLibrary.VehicleParts.Collections;
using DriveBeamEditor.Wpf;
using JBeamLibrary.VehicleParts.Elements;
using JBeamLibrary.Tools;
using System.Windows.Media.Media3D;
using JBeamLibrary.VehicleParts.Interfaces;
using JBeamLibrary.Types;

namespace DriveBeamEditor.Controls.BeamNG.Controls
{
    public partial class NodeBeam3DEditorControl : VehiclePartControl
    {
        #region Properties

        public float Scale3D
        {
            get { return nodeBeam3DEditor.Scale3D; }
        }

        #endregion

        #region CTORs

        public NodeBeam3DEditorControl()
        {
            InitializeComponent();
            SetupEvents();
        }

        #endregion

        #region Methods

        public override string ToString()
        {
            return "3D Editor";
        }

        private void SetupEvents()
        {
            /* New node added */
            nodeBeam3DEditor.NodeAdded += (s, n) =>
                {
                    if (VehiclePart == null) return;
                    var x = (float)(n.X / Scale3D);
                    var y = (float)(n.Y / Scale3D);
                    var z = (float)(n.Z / Scale3D);
                    var node = new Node(
                        nodeBeam3DEditor.PrefixNodes ? Utils.GetNewId(nodeBeam3DEditor.NodePrefix, VehiclePart.NodeSection.NumericNodes) : Utils.GetNewId(VehiclePart.NodeSection.Count),
                        x, y, z);
                    VehiclePart.NodeSection.AddElement(node);
                    nodeBeam3DEditor.SetNames(n, VehiclePart.NodeSection.Nodes.Where(nd => nd.PosX == x && nd.PosY == y && nd.PosZ == z).Select(nd => nd.Id));
                };

            /* Node removed */
            nodeBeam3DEditor.NodeRemoved += (s, n) =>
                {
                    if (VehiclePart == null) return;

                    var node = GetNodeFromPos(n);
                    if (node == null) return;

                    VehiclePart.NodeSection.RemoveElement(node);
                    nodeBeam3DEditor.SetNames(n, null);
                };

            /* Node moved */
            nodeBeam3DEditor.NodeMoved += (s, t) =>
                {
                    if (VehiclePart == null) return;

                    var node = GetNodeFromPos(t.Item1);
                    if (node == null) return;

                    var pos = t.Item2.ToFloat3(Scale3D);
                    node.PosX = pos.X;
                    node.PosY = pos.Y;
                    node.PosZ = pos.Z;

                    nodeBeam3DEditor.SetNames(t.Item1, null);
                    nodeBeam3DEditor.SetNames(t.Item2, new[] { node.Id });
                };

            /* Nodes moved */
            nodeBeam3DEditor.NodesMoved += (s, l) =>
            {
                if (VehiclePart == null) return;
                foreach (var t in l)
                {
                    var node = GetNodeFromPos(t.Item1);
                    if (node == null) return;

                    var pos = t.Item2.ToFloat3(Scale3D);
                    node.PosX = pos.X;
                    node.PosY = pos.Y;
                    node.PosZ = pos.Z;
                }
            };

            /* New beam added */
            nodeBeam3DEditor.BeamAdded += (s, b) =>
                {
                    if (VehiclePart == null) return;

                    var n1 = GetNodeFromPos(b.Item1);
                    var n2 = GetNodeFromPos(b.Item2);
                    if (n1 == null || n2 == null) return;

                    var beam = new Beam(n1.Id, n2.Id);
                    VehiclePart.BeamSection.AddElement(beam);
                };

            /* Beam removed */
            nodeBeam3DEditor.BeamRemoved += (s, b) =>
                {
                    if (VehiclePart == null) return;

                    var n1 = GetNodeFromPos(b.Item1);
                    var n2 = GetNodeFromPos(b.Item2);
                    if (n1 == null || n2 == null) return;

                    var beams = ((BeamCollection)VehiclePart.BeamSection.ElementCollection).Where(beam => beam.NodeId1.Equals(n1.Id) && beam.NodeId2.Equals(n2.Id));
                    for (var i = 0; i < beams.Count(); i++)
                    {
                        VehiclePart.BeamSection.ElementCollection.Remove(beams.ElementAt(i));
                    }
                };

            /* New triangle added */
            nodeBeam3DEditor.TriangleAdded += (s, t) =>
            {
                if (VehiclePart == null) return;

                var n1 = GetNodeFromPos(t.Item1);
                var n2 = GetNodeFromPos(t.Item2);
                var n3 = GetNodeFromPos(t.Item3);

                /* Triangle cannot be added because it was not created from 3 nodes */
                if (n1 == null || n2 == null || n3 == null)
                {
                    nodeBeam3DEditor.RemoveTriangle(t.Item1, t.Item2, t.Item3, true);
                    return;
                }

                var triangle = new Triangle(n1.Id, n2.Id, n3.Id);
                VehiclePart.TriangleSection.AddElement(triangle);
            };

            /* New thruster added */
            nodeBeam3DEditor.ThrusterAdded += (s, t) =>
            {
                if (VehiclePart == null) return;

                var n1 = GetNodeFromPos(t.Item1);
                var n2 = GetNodeFromPos(t.Item2);

                /* Thruster cannot be added because it was not created with 2 nodes */
                if (n1 == null || n2 == null)
                {
                    return;
                }

                var thruster = new Thruster(n1.Id, n2.Id);
                VehiclePart.ThrusterSection.AddElement(thruster);
            };

            /* Beam removed */
            nodeBeam3DEditor.TriangleRemoved += (s, t) =>
            {
                if (VehiclePart == null) return;

                var n1 = GetNodeFromPos(t.Item1);
                var n2 = GetNodeFromPos(t.Item2);
                var n3 = GetNodeFromPos(t.Item3);

                if (n1 == null || n2 == null || n3 == null) return;

                var triangles =
                    ((TriangleCollection)VehiclePart.TriangleSection.ElementCollection).Where(
                        triangle =>
                            triangle.NodeId1.Equals(n1.Id) && triangle.NodeId2.Equals(n2.Id) &&
                            triangle.NodeId3.Equals(n3.Id));

                for (var i = 0; i < triangles.Count(); i++)
                {
                    VehiclePart.TriangleSection.ElementCollection.Remove(triangles.ElementAt(i));
                }
            };

            /* External camera was changed */
            nodeBeam3DEditor.ExternalCameraChanged += (s, e) =>
            {
                if (VehiclePart == null) return;

                var origin = e.Item1;
                var fov = e.Item2;

                /* Remove the current camera in the editor */
                nodeBeam3DEditor.RemoveCamera(VehiclePart.CameraExternal.Offset.ToPoint3D(Scale3D), true);

                /* Update the existing one */
                VehiclePart.CameraExternal.FOV = e.Item2;
                VehiclePart.CameraExternal.Offset = e.Item1.ToFloat3();
            };

            /* An internal camera was added */
            nodeBeam3DEditor.InternalCameraAdded += (s, e) =>
            {
                if (VehiclePart == null) return;

                /* Retrieve data */
                var origin = e.Item1.ToFloat3(Scale3D);
                var fov = e.Item2;
                var node1 = GetNodeFromPos(e.Item3.Item1);
                var node2 = GetNodeFromPos(e.Item3.Item2);
                var node3 = GetNodeFromPos(e.Item3.Item3);
                var node4 = GetNodeFromPos(e.Item3.Item4);
                var node5 = GetNodeFromPos(e.Item3.Item5);
                var node6 = GetNodeFromPos(e.Item3.Item6);

                /* Couldn't find some nodes */
                if (node1 == null || node2 == null || node3 == null || node4 == null || node5 == null || node6 == null)
                    return;

                var cam = new InternalCamera(origin, fov, node1.Id, node2.Id, node3.Id, node4.Id, node5.Id, node6.Id);
                VehiclePart.InternalCameraSection.AddElement(cam);
            };

            nodeBeam3DEditor.QueryTriangles += (s, e) => BindTriangles();
            nodeBeam3DEditor.QueryReload += (s, e) => OnVehiclePartChanged();
            nodeBeam3DEditor.QueryLoadModel += (s, e) => BindModel();

            /* Adds a part's data to the 3D editor */
            nodeBeam3DEditor.PartSelected += (s, p) =>
            {
                if (VehiclePart == null) return;

                var part = VehiclePart.File.Parent.FindPart(p);
                if (part == null) return;

                var nodePoints = ((NodeCollection)part.NodeSection.ElementCollection).ToPoints(Scale3D);
                var beamPoints = ((BeamCollection)part.BeamSection.ElementCollection).ToPoints(VehiclePart.File.Parent, Scale3D);
                nodeBeam3DEditor.AddPart(p, nodePoints, beamPoints);
            };

            /* The control has requested text information about a node */
            nodeBeam3DEditor.QueryNodeInfo += (s, n) =>
            {
                if (VehiclePart == null) return;

                var node = GetNodeFromPos(n);
                if (node == null) return;

                var builder = new StringBuilder("NODE INFO");
                builder.AppendFormat("{0}{0}ID: {1}", Environment.NewLine, node.Id);
                builder.AppendFormat("{0}Pos: X={1}, Y={2}, Z={3}", Environment.NewLine, node.PosX, node.PosY, node.PosZ);

                /* Properties */
                foreach (var prop in GroupHelper.GetElementProperties(VehiclePart.NodeSection, node))
                    builder.AppendFormat("{0}{1}: {2} ({3})", Environment.NewLine, prop.Name, prop.Value.ToJBeam(), prop.Use);

                nodeBeam3DEditor.SetNodeInfo(n, builder.ToString());
            };

            /* The control has requested text information about a beam */
            nodeBeam3DEditor.QueryBeamsInfo += (s, b) =>
            {
                if (VehiclePart == null) return;

                var n1 = GetNodeFromPos(b.Item1);
                var n2 = GetNodeFromPos(b.Item2);

                if (n1 == null || n2 == null) return;

                var beams = ((BeamCollection)VehiclePart.BeamSection.ElementCollection).Where(beam => beam.NodeId1.Equals(n1.Id) && beam.NodeId2.Equals(n2.Id));

                var builder = new StringBuilder();
                for (var i = 0; i < beams.Count(); i++)
                {
                    var beam = beams.ElementAt(i);
                    builder.AppendFormat("BEAM INFO{0}", Environment.NewLine);
                    builder.AppendFormat("{0}NODE 1: {1}", Environment.NewLine, beam.NodeId1);
                    builder.AppendFormat("{0}NODE 2: {1}", Environment.NewLine, beam.NodeId2);

                    /* Properties */
                    foreach (var prop in GroupHelper.GetElementProperties(VehiclePart.BeamSection, beam))
                        builder.AppendFormat("{0}{1}: {2} ({3})", Environment.NewLine, prop.Name, prop.Value.ToJBeam(), prop.Use);

                    builder.AppendLine();
                }
                nodeBeam3DEditor.SetBeamInfo(b.Item1, b.Item2, builder.ToString());
            };

            /* The control has requested text information about a triangle */
            nodeBeam3DEditor.QueryTriangleInfo += (s, t) =>
            {
                if (VehiclePart == null) return;

                var n1 = GetNodeFromPos(t.Item1);
                var n2 = GetNodeFromPos(t.Item2);
                var n3 = GetNodeFromPos(t.Item3);

                if (n1 == null || n2 == null || n3 == null) return;

                var triangle = ((TriangleCollection)VehiclePart.TriangleSection.ElementCollection).FirstOrDefault(tri => tri.NodeId1.Equals(n1.Id) && tri.NodeId2.Equals(n2.Id) && tri.NodeId3.Equals(n3.Id));
                if (triangle == null) return;

                var builder = new StringBuilder();
                builder.AppendFormat("TRIANGLE INFO{0}", Environment.NewLine);
                builder.AppendFormat("{0}NODE 1: {1}", Environment.NewLine, triangle.NodeId1);
                builder.AppendFormat("{0}NODE 2: {1}", Environment.NewLine, triangle.NodeId2);
                builder.AppendFormat("{0}NODE 3: {1}", Environment.NewLine, triangle.NodeId2);

                /* Properties */
                foreach (var prop in GroupHelper.GetElementProperties(VehiclePart.TriangleSection, triangle))
                    builder.AppendFormat("{0}{1}: {2} ({3})", Environment.NewLine, prop.Name, prop.Value.ToJBeam(), prop.Use);

                builder.AppendLine();
                nodeBeam3DEditor.SetTriangleInfo(t.Item1, t.Item2, t.Item3, builder.ToString());
            };
        }

        private void BindNodes()
        {
            nodeBeam3DEditor.ClearNodes();
            if (VehiclePart == null || VehiclePart.NodeSection.Count == 0) return;

            var points = ((NodeCollection)VehiclePart.NodeSection.ElementCollection).ToPoints(Scale3D);
            nodeBeam3DEditor.AddNodes(points, true);
            nodeBeam3DEditor.UpdateNames(VehiclePart.NodeSection.Nodes.Select(node => new Tuple<Point3D, string>(node.ToPoint3D(Scale3D), node.Id)));
        }

        private void BindBeams()
        {
            nodeBeam3DEditor.ClearBeams();
            if (VehiclePart == null || VehiclePart.BeamSection.Count == 0) return;

            var points = ((BeamCollection)VehiclePart.BeamSection.ElementCollection).ToPoints(VehiclePart.File.Parent, Scale3D);
            nodeBeam3DEditor.AddBeams(points, true);
        }

        private void BindTriangles()
        {
            nodeBeam3DEditor.ClearTriangles();
            nodeBeam3DEditor.ClearNormals();
            if (VehiclePart == null || VehiclePart.TriangleSection.Count == 0) return;

            var triangles =
                ((TriangleCollection)VehiclePart.TriangleSection.ElementCollection).Aggregate(
                    new List<Tuple<Point3D, Point3D, Point3D>>(),
                    (list, cur) =>
                    {
                        var node1 = Vehicle.FindNode(cur.NodeId1);
                        var node2 = Vehicle.FindNode(cur.NodeId2);
                        var node3 = Vehicle.FindNode(cur.NodeId3);
                        if (node1 == null || node2 == null || node3 == null) return list;
                        list.Add(new Tuple<Point3D, Point3D, Point3D>(node1.ToPoint3D(Scale3D), node2.ToPoint3D(Scale3D), node3.ToPoint3D(Scale3D)));
                        return list;
                    });
            nodeBeam3DEditor.AddTriangles(triangles, true);
        }

        private void BindThrusters()
        {
            nodeBeam3DEditor.ClearThrusters();
            if (VehiclePart == null || VehiclePart.ThrusterSection.Count == 0) return;

            var points = ((ThrusterCollection)VehiclePart.ThrusterSection.ElementCollection).ToPoints(VehiclePart.File.Parent, Scale3D);
            nodeBeam3DEditor.AddThrusters(points, true);
        }

        private void BindCameras()
        {
            nodeBeam3DEditor.ClearCameras();

            if (VehiclePart == null || (VehiclePart.InternalCameraSection.Count == 0 && !VehiclePart.CameraExternal.IsDefined)) return;

            /* Bind the external camera */
            if (VehiclePart.CameraExternal.IsDefined)
            {
                nodeBeam3DEditor.AddCamera(VehiclePart.CameraExternal.Offset.ToPoint3D(Scale3D), VehiclePart.CameraExternal.FOV, true);
            }

            /* Bind the internal cameras */
            foreach (var cam in (InternalCameraCollection)VehiclePart.InternalCameraSection.ElementCollection)
            {
                Point3D? origin;
                var nodes = cam.ToPoints(Vehicle, out origin, Scale3D);
                nodeBeam3DEditor.AddCamera(origin.Value, cam.FOV, nodes, true);
            }
        }

        private void BindParts()
        {
            var partList = new List<Tuple<string, string>>();
            if (VehiclePart != null)
                partList.AddRange(VehiclePart.File.Parent.AllParts.Select(part => new Tuple<string, string>(part.Name, part.Description)));
            nodeBeam3DEditor.SetPartsList(partList);
        }

        private void BindModel()
        {
            nodeBeam3DEditor.CurrentModel = null;

            if (Vehicle == null) return;

            var modelPaths = Vehicle.ReadableModelFilePaths;
            if (!modelPaths.Any()) return;

            //nodeBeam3DEditor.LoadModel(modelPaths.First().Replace(@"\\", @"\"));
            nodeBeam3DEditor.LoadModels(modelPaths.Select(path => path.Replace(@"\\", @"\")));
        }

        protected override void OnVehiclePartChanged()
        {
            BindModel();
            BindNodes();
            BindBeams();
            BindTriangles();
            BindThrusters();
            BindCameras();
            BindParts();
            base.OnVehiclePartChanged();
        }

        private Node GetNodeFromPos(float x, float y, float z)
        {
            if (VehiclePart == null) return null;
            return VehiclePart.NodeSection.Nodes
                .FirstOrDefault(node => node.PosX == x && node.PosY == y && node.PosZ == z)
                ?? Vehicle.FindNode(x, y, z);
        }

        private Node GetNodeFromPos(Float3 pos)
        {
            return GetNodeFromPos(pos.X, pos.Y, pos.Z);
        }

        private Node GetNodeFromPos(Point3D pos)
        {
            return GetNodeFromPos(pos.ToFloat3(Scale3D));
        }

        public override void RefreshData()
        {
            base.RefreshData();
            nodeBeam3DEditor.UpdateVisibility();
        }

        #endregion
    }
}
